import { MapVision } from '@/global/map';
import { collectStatistics } from '@/global/stats';
import { profile } from '@/utils/decorators';
import { runIntervally } from '@/utils/functions/inInterval';
import { Logger } from '@/utils/logger';
import { runErrorMapped } from '@/utils/wrapper';
import { Colony } from './Colony';
import { ColonyFactory } from './ColonyFactory';
import { executeFlagCommands } from './FlagCommands';
import { NormalColony } from './instances/normal/NormalColony';

const creepIdByColony: Record<string, Id<Creep>[]> = {};

/**
 * 帝国对象，管理各个 `Colony` 殖民地对象
 */
export class EmpireProto {

    colonies: Record<string, Colony>;

    constructor() {
        this.colonies = {};
        this.initializeMemory();
    }

    /**
     * 初始化构建殖民地信息
     */
    @profile
    build() {
        this.registerColonies();                // 注册殖民地
        this.registerCreepsByColony();          // 注册 creeps
        Object.values(this.colonies)
            .forEach(c => c.build());           // 构造数据结构
        this.needsRebuilding = false;
    }

    /**
     * 每 tick 刷新殖民地信息，减少每次 build 的 CPU 消耗
     */
    @profile
    refresh() {
        Object.values(this.colonies)
            .forEach(c => c.refresh());         // 构造数据结构刷新
        Object.values(Game.creeps).forEach(creep => {
            creep.declareTargeting();           // 刷新追踪对象
        });
    }

    /**
     * 运行殖民地的操作
     */
    @profile
    run() {
        // 检测爬的数量有没有变化
        const currentCreepNum = Object.keys(Game.creeps).length;
        if (currentCreepNum !== Memory.lastCreepNum) {
            Memory.lastCreepNum = currentCreepNum;
            Game.hasCreepNumChanged = true;
        }
        // 运行殖民地
        Object.values(this.colonies).forEach(colony => {
            // note! 不能写成 runErrorMapped(colony.run)，不然 this 是 undefined
            runErrorMapped(() => colony.run());
        });
        // 在殖民地运行完后刷新爬的信息
        // 如果有爬 spawn 出来，那么也设置 Game.hasCreepNumChanged = true
        if (Game.hasCreepNumChanged) {
            this.registerCreepsByColony();
        }
    }

    /**
     * 清理善后
     */
    cleanUp() {
        this.cleanDeadObjects();
        MapVision.process(); // 处理视野库写入
        this.processFlagCommands();
        Memory.settings = settings; // 回写设置
        collectStatistics();        // 统计数据
    }

    public needsRebuilding = true;

    private initializeMemory() {
        Memory.colony ??= {};
        if (!Memory.botBirthTick) {
            Memory.botBirthTick = Game.time;
            Logger.info(`RedefinedBot birth tick: ${Game.time}`);
        }
    }

    private registerCreepsByColony() {
        let total = 0;
        for (const creepName in Game.creeps) {
            const creep = Game.creeps[creepName];
            const home = creep.memory.home ?? 'homeless';
            if (!creepIdByColony[home]) {
                creepIdByColony[home] = [];
            }
            creepIdByColony[home].push(creep.id);
            total++;
        }
        Memory.lastCreepNum = total;
    }

    private cleanDeadObjects() {
        const clean = (mem: object, gm: object, callback?: (name: string) => void) => {
            for (const name in mem) {
                if (!gm[name]) {
                    callback?.(name);
                    delete mem[name];
                }
            }
        };
        runIntervally(IntervalLength.medium, function () {
            // 清理死去的爬的内存
            clean(Memory.creeps, Game.creeps);
            // 清理不存在的旗帜
            clean(Memory.flags, Game.flags);
            // 清除不存在的 Spawn
            clean(Memory.spawns, Game.spawns, spawnName => {
                Logger.warn(`Spawn "${spawnName}" 不存在，请检查是否被摧毁`, 'empire', true);
            });
        });
    }

    private registerColonies() {
        const factory = new ColonyFactory;
        for (const roomName in Game.rooms) {
            const room = Game.rooms[roomName];
            if (!room.controller?.my) {
                continue;
            }
            let outpostNames: string[] = [];
            if (!Memory.colony[roomName]) {
                Memory.colony[roomName] = { originalLevel: 1 };
            }
            const mem = Memory.colony[roomName];
            if (mem) {
                outpostNames = mem.outpostNames;
            }
            const colony = factory.produce(roomName, outpostNames, mem.operatingMode ?? 'normal');
            colony.build();
            this.colonies[roomName] = colony;
        }
    }

    private processFlagCommands() {
        executeFlagCommands();
    }

    getCreepsByColony(colonyName: string) {
        const ids = creepIdByColony[colonyName] || [];
        return ids.map(id => Game.getObjectById(id));
    }

}